package com.github.xiaomaoguai.xxr.utils.http;

import org.apache.http.HttpException;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.HttpConnectionFactory;
import org.apache.http.conn.ManagedHttpClientConnection;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.ManagedHttpClientConnectionFactory;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.conn.SystemDefaultDnsResolver;
import org.apache.http.impl.io.DefaultHttpRequestWriterFactory;
import org.apache.http.impl.io.DefaultHttpResponseParserFactory;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * HttpClientUtil--工具类 参考开涛书籍
 *
 * @author: WeiHui-Z
 * @version: v1.0.0
 */
public final class HttpClientUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(HttpClientUtil.class);

    /**
     * 编码
     */
    private static final String UTF_8 = "UTF-8";

    private static PoolingHttpClientConnectionManager manager = null;

    private static CloseableHttpClient httpClient = null;

    /**
     * 私有构造方法
     */
    private HttpClientUtil() {
    }

    private static synchronized CloseableHttpClient getHttpClient() {
        if (httpClient == null) {
            //注册访问协议相关的socket工厂
            Registry<ConnectionSocketFactory> socketFactoryRegistry =
                    RegistryBuilder.<ConnectionSocketFactory>create()
                            .register("http", PlainConnectionSocketFactory.INSTANCE)
                            .register("https", SSLConnectionSocketFactory.getSystemSocketFactory())
                            .build();
            //HttpConnection 工厂 :配置写请求/解析响应处理器
            HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connectionFactory = new ManagedHttpClientConnectionFactory(DefaultHttpRequestWriterFactory.INSTANCE, DefaultHttpResponseParserFactory.INSTANCE);
            //DNS 解析器
            SystemDefaultDnsResolver dnsResolver = SystemDefaultDnsResolver.INSTANCE;

            manager = new PoolingHttpClientConnectionManager(socketFactoryRegistry, connectionFactory, dnsResolver);

            //默认为socket配置
            SocketConfig socketConfig = SocketConfig.custom().setTcpNoDelay(true).build();
            manager.setDefaultSocketConfig(socketConfig);
            //设置整个连接池的最大连接数
            manager.setMaxTotal(1000);
            //每个路由的默认最大连接，每个路由实际最大连接数默认为DefaultMaxPerRoute控制，而MaxTotal是控制整个池子最大数
            manager.setDefaultMaxPerRoute(200);
            //在从连接池获取链接时，连接不活跃多长时间后需要进行一次验证，默认为2s
            manager.setValidateAfterInactivity(5 * 1000);

            //默认请求配置  //设置连接超时时间,2s
            RequestConfig defaultRequestConfig = RequestConfig.custom().setConnectTimeout(2 * 1000)
                    //设置等待数据超时时间，5s
                    .setSocketTimeout(5 * 1000)
                    //设置从连接池获取连接的等待超时时间
                    .setConnectionRequestTimeout(2000).build();

            httpClient = HttpClients.custom().setConnectionManager(manager)
                    //连接池不是共享模式
                    .setConnectionManagerShared(false)
                    .evictIdleConnections(60, TimeUnit.SECONDS)
                    //定期回收过期连接
                    .evictExpiredConnections()
                    .setConnectionTimeToLive(60, TimeUnit.SECONDS)
                    //设置默认请求配置
                    .setDefaultRequestConfig(defaultRequestConfig)
                    //连接重用策略，即是否能 keepAlive
                    .setConnectionReuseStrategy(DefaultConnectionReuseStrategy.INSTANCE)
                    //长连接配置，即获取长连接生产多长时间
                    .setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE)
                    //设置重试次数
                    .setRetryHandler(new DefaultHttpRequestRetryHandler(0, false)).build();

            //JVM 停止或重启时，关闭连接池释放掉连接
            Runtime.getRuntime().addShutdownHook(new Thread() {

                @Override
                public void run() {
                    try {
                        httpClient.close();
                    } catch (IOException e) {
                        LOGGER.error("关闭httpClient出错.", e);
                    }
                }
            });
        }
        return httpClient;
    }

    /**
     * 获取请求参数
     *
     * @param parameterMap 参数列表
     * @return 请求参数
     */
    private static List<NameValuePair> getParam(Map<String, String> parameterMap) {
        List<NameValuePair> nameValuePairs = new ArrayList<>();
        if (parameterMap != null) {
            Set<String> keySet = parameterMap.keySet();
            for (String key : keySet) {
                nameValuePairs.add(new BasicNameValuePair(key, parameterMap.get(key)));
            }
        }
        return nameValuePairs;
    }

    /**
     * @param url http 请求链接
     * @return 请求结果
     * @throws org.apache.http.HttpException ex
     */
    public static String doGet(String url) throws HttpException {
        String result = null;
        LOGGER.debug("HttpClientUtil-doGet-请求链接:[{}]", url);
        HttpGet httpGet = new HttpGet(url);
        try {
            CloseableHttpResponse response = getHttpClient().execute(httpGet);
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                result = EntityUtils.toString(response.getEntity());
            } else {
                //请求不为200， 即请求Error 时
                EntityUtils.consume(response.getEntity());
            }
        } catch (IOException e) {
            LOGGER.error("HttpClientUtil-doGet请求错误,请求链接:[{}]", url, e);
            //抛出自定义异常，方便系统处理
            throw new HttpException("HttpClientUtil-doGet-请求错误", e);
        }
        return result;
    }

    /**
     * Get 请求 with param
     *
     * @param url   请求Url
     * @param param 请求参数
     * @return 请求结果
     * @throws org.apache.http.HttpException ex
     */
    public static String doGet(String url, Map<String, String> param) throws HttpException {
        StringBuilder builder = new StringBuilder(url);
        builder.append("?");
        for (Map.Entry<String, String> entry : param.entrySet()) {
            builder.append(entry.getKey());
            builder.append("=");
            builder.append(entry.getValue());
            builder.append("&");
        }
        return doGet(builder.toString());
    }

    /**
     * @param url    请求Url
     * @param params 请求参数
     * @return 返回结果
     * @throws org.apache.http.HttpException ex
     */
    public static String doPost(String url, Map<String, String> params) throws HttpException {
        String result = null;
        LOGGER.debug("HttpClientUtil-doPost-请求链接:[{}],请求参数:[{}]", url, params);
        HttpPost httpPost = new HttpPost(url);
        try {
            UrlEncodedFormEntity postEntity = new UrlEncodedFormEntity(getParam(params), UTF_8);
            httpPost.setEntity(postEntity);
            CloseableHttpResponse response = getHttpClient().execute(httpPost);
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                result = EntityUtils.toString(response.getEntity());
            } else {
                //请求不为200， 即请求Error 时
                EntityUtils.consume(response.getEntity());
            }
        } catch (IOException e) {
            LOGGER.error("HttpClientUtil-doPost-请求链接:[{}]", url, e);
            //抛出自定义异常，方便系统处理
            throw new HttpException("HttpClientUtil-doPost-请求错误", e);
        }
        return result;
    }

	/**
	 * @param url    请求Url
	 * @return 返回结果
	 * @throws org.apache.http.HttpException ex
	 */
	public static String doPost(String url, String  requestBody) throws HttpException {
		String result = null;
		LOGGER.debug("HttpClientUtil-doPost-请求链接:[{}],请求参数:[{}]", url, requestBody);
		HttpPost httpPost = new HttpPost(url);
		try {
			httpPost.setEntity(new StringEntity(requestBody, "UTF-8"));
			CloseableHttpResponse response = getHttpClient().execute(httpPost);
			if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
				result = EntityUtils.toString(response.getEntity());
			} else {
				//请求不为200， 即请求Error 时
				EntityUtils.consume(response.getEntity());
			}
		} catch (IOException e) {
			LOGGER.error("HttpClientUtil-doPost-请求链接:[{}]", url, e);
			//抛出自定义异常，方便系统处理
			throw new HttpException("HttpClientUtil-doPost-请求错误", e);
		}
		return result;
	}

}
